{
 "cells": [
  {
   "cell_type": "code",
   "execution_count": 1,
   "metadata": {},
   "outputs": [],
   "source": [
    "\n",
    "import torch\n",
    "import triton\n",
    "import triton.language as tl\n",
    "from copy import deepcopy\n",
    "import os"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "- 一共5个版本，v1v2v3性能较差，但是没有删，感觉更能体现实现一个kernel的过程和优化\n",
    "- v4v5性能相当，建议使用v4进行跑benchmark"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 5,
   "metadata": {},
   "outputs": [],
   "source": [
    "\n",
    "def rotate_half(x):\n",
    "    \"\"\"Rotates half the hidden dims of the input.\"\"\"\n",
    "    x1 = x[..., : x.shape[-1] // 2]\n",
    "    x2 = x[..., x.shape[-1] // 2 :]\n",
    "    return torch.cat((-x2, x1), dim=-1)\n",
    "\n",
    "def repeat_kv(hidden_states: torch.Tensor, n_rep: int) -> torch.Tensor:\n",
    "    \"\"\"\n",
    "    This is the equivalent of torch.repeat_interleave(x, dim=1, repeats=n_rep). The hidden states go from (batch,\n",
    "    num_key_value_heads, seqlen, head_dim) to (batch, num_attention_heads, seqlen, head_dim)\n",
    "    \"\"\"\n",
    "    batch, num_key_value_heads, slen, head_dim = hidden_states.shape\n",
    "    if n_rep == 1:\n",
    "        return hidden_states\n",
    "    hidden_states = hidden_states[:, :, None, :, :].expand(batch, num_key_value_heads, n_rep, slen, head_dim)\n",
    "    return hidden_states.reshape(batch, num_key_value_heads * n_rep, slen, head_dim)\n",
    "\n",
    "def rotate_half_v2(x):\n",
    "    \"\"\"Rotates half the hidden dims of the input.\"\"\"\n",
    "    x1 = x[..., : x.shape[-1] // 2]\n",
    "    x2 = x[..., x.shape[-1] // 2 :]\n",
    "    return torch.cat((x2, -x1), dim=-1)\n",
    "\n",
    "def rotate_half_v3(x):\n",
    "    \"\"\"Rotates half the hidden dims of the input.\"\"\"\n",
    "    x1 = x[..., : x.shape[-1] // 2]\n",
    "    x2 = x[..., x.shape[-1] // 2 :]\n",
    "    return torch.cat((x2, x1), dim=-1)\n",
    "\n",
    "def apply_rotary_pos_emb(q, k, cos, sin, position_ids=None, unsqueeze_dim=1):\n",
    "    \"\"\"Applies Rotary Position Embedding to the query and key tensors.\n",
    "\n",
    "    Args:\n",
    "        q (`torch.Tensor`): The query tensor.\n",
    "        k (`torch.Tensor`): The key tensor.\n",
    "        cos (`torch.Tensor`): The cosine part of the rotary embedding.\n",
    "        sin (`torch.Tensor`): The sine part of the rotary embedding.\n",
    "        position_ids (`torch.Tensor`, *optional*):\n",
    "            Deprecated and unused.\n",
    "        unsqueeze_dim (`int`, *optional*, defaults to 1):\n",
    "            The 'unsqueeze_dim' argument specifies the dimension along which to unsqueeze cos[position_ids] and\n",
    "            sin[position_ids] so that they can be properly broadcasted to the dimensions of q and k. For example, note\n",
    "            that cos[position_ids] and sin[position_ids] have the shape [batch_size, seq_len, head_dim]. Then, if q and\n",
    "            k have the shape [batch_size, heads, seq_len, head_dim], then setting unsqueeze_dim=1 makes\n",
    "            cos[position_ids] and sin[position_ids] broadcastable to the shapes of q and k. Similarly, if q and k have\n",
    "            the shape [batch_size, seq_len, heads, head_dim], then set unsqueeze_dim=2.\n",
    "    Returns:\n",
    "        `tuple(torch.Tensor)` comprising of the query and key tensors rotated using the Rotary Position Embedding.\n",
    "    \"\"\"\n",
    "    cos = cos.unsqueeze(unsqueeze_dim)\n",
    "    sin = sin.unsqueeze(unsqueeze_dim)\n",
    "    q_embed = (q * cos) + (rotate_half(q) * sin)\n",
    "    k_embed = (k * cos) + (rotate_half(k) * sin)\n",
    "    return q_embed, k_embed"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# v1"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 6,
   "metadata": {},
   "outputs": [],
   "source": [
    "\n",
    "\n",
    "@triton.jit\n",
    "def _fused_apply_rope_fwd(X, X_R, X_EMBED, COS, SIN,\n",
    "                        stride_xb, stride_xl, stride_xh, stride_xd,\n",
    "                        stride_cb, stride_cl, stride_cd,\n",
    "                        B, COS_B, L, H, D:tl.constexpr, BLOCK_H:  tl.constexpr,\n",
    "                        ):\n",
    "    pid = tl.program_id(0)\n",
    "    off_l = pid % L\n",
    "    x_offset = pid * stride_xl\n",
    "    if B == COS_B:\n",
    "        cos_offset = pid * stride_cl\n",
    "    else:\n",
    "        cos_offset = off_l * stride_cl\n",
    "    # cos_offset = pid * stride_cl\n",
    "    X += x_offset\n",
    "    X_R += x_offset\n",
    "    X_EMBED += x_offset\n",
    "    COS += cos_offset\n",
    "    SIN += cos_offset\n",
    "    cols = tl.arange(0, D)\n",
    "    cos_ptrs = COS + cols\n",
    "    sin_ptrs = SIN + cols\n",
    "\n",
    "    x_ptrs = tl.make_block_ptr(\n",
    "        base=X,\n",
    "        shape=(H, D),\n",
    "        offsets=(0, 0),\n",
    "        strides=(stride_xh, stride_xd),\n",
    "        block_shape=(BLOCK_H, D),\n",
    "        order=(1,0)\n",
    "    )\n",
    "    xr_ptrs = tl.make_block_ptr(\n",
    "        base=X_R,\n",
    "        shape=(H, D),\n",
    "        offsets=(0, 0),\n",
    "        strides=(stride_xh, stride_xd),\n",
    "        block_shape=(BLOCK_H, D),\n",
    "        order=(1,0)\n",
    "    )\n",
    "    xembed_ptrs = tl.make_block_ptr(\n",
    "        base=X_EMBED,\n",
    "        shape=(H, D),\n",
    "        offsets=(0, 0),\n",
    "        strides=(stride_xh, stride_xd),\n",
    "        block_shape=(BLOCK_H, D),\n",
    "        order=(1,0)\n",
    "    )\n",
    "    cos = tl.load(cos_ptrs)\n",
    "    sin = tl.load(sin_ptrs)\n",
    "    x = tl.load(x_ptrs, boundary_check=(0,), padding_option='zero')\n",
    "    xr = tl.load(xr_ptrs, boundary_check=(0,), padding_option='zero')\n",
    "    x_embed = x * cos[None, :] + xr * sin[None, :]\n",
    "    tl.store(xembed_ptrs, x_embed, boundary_check=(0,))\n",
    "\n",
    "@triton.jit\n",
    "def _fused_apply_rope_bwd(DX_EMBED, DX_EMBED_R, DX, COS, SIN,\n",
    "                        stride_xb, stride_xl, stride_xh, stride_xd,\n",
    "                        stride_cb, stride_cl, stride_cd,\n",
    "                        B, COS_B, L, H, D:tl.constexpr, BLOCK_H:  tl.constexpr,\n",
    "                        ):\n",
    "    pid = tl.program_id(0)\n",
    "    off_l = pid % L\n",
    "    x_offset = pid * stride_xl\n",
    "    if B == COS_B:\n",
    "        cos_offset = pid * stride_cl\n",
    "    else:\n",
    "        cos_offset = off_l * stride_cl\n",
    "    # cos_offset = pid * stride_cl\n",
    "    DX += x_offset\n",
    "    DX_EMBED += x_offset\n",
    "    DX_EMBED_R += x_offset\n",
    "    COS += cos_offset\n",
    "    SIN += cos_offset\n",
    "    cols = tl.arange(0, D)\n",
    "    cos_ptrs = COS + cols\n",
    "    sin_ptrs = SIN + cols\n",
    "\n",
    "    dx_ptrs = tl.make_block_ptr(\n",
    "        base=DX,\n",
    "        shape=(H, D),\n",
    "        offsets=(0, 0),\n",
    "        strides=(stride_xh, stride_xd),\n",
    "        block_shape=(BLOCK_H, D),\n",
    "        order=(1,0)\n",
    "    )\n",
    "    dx_embed_r_ptrs = tl.make_block_ptr(\n",
    "        base=DX_EMBED_R,\n",
    "        shape=(H, D),\n",
    "        offsets=(0, 0),\n",
    "        strides=(stride_xh, stride_xd),\n",
    "        block_shape=(BLOCK_H, D),\n",
    "        order=(1,0)\n",
    "    )\n",
    "    dx_embed_ptrs = tl.make_block_ptr(\n",
    "        base=DX_EMBED,\n",
    "        shape=(H, D),\n",
    "        offsets=(0, 0),\n",
    "        strides=(stride_xh, stride_xd),\n",
    "        block_shape=(BLOCK_H, D),\n",
    "        order=(1,0)\n",
    "    )\n",
    "    cos = tl.load(cos_ptrs)\n",
    "    sin = tl.load(sin_ptrs)\n",
    "    dx_embed_r = tl.load(dx_embed_r_ptrs, boundary_check=(0,), padding_option='zero')\n",
    "    dx_embed = tl.load(dx_embed_ptrs, boundary_check=(0,), padding_option='zero')\n",
    "    dx = dx_embed * cos[None, :] + dx_embed_r * sin[None, :]\n",
    "    tl.store(dx_ptrs, dx, boundary_check=(0,))\n",
    "\n",
    "class _FusedApplyRope(torch.autograd.Function):\n",
    "    @staticmethod\n",
    "    def forward(ctx, q, k, cos, sin):\n",
    "        q = q.transpose(1,2)\n",
    "        k = k.transpose(1,2)\n",
    "        assert q.is_contiguous() and k.is_contiguous()\n",
    "        qh = q.size(2)\n",
    "        kh = k.size(2)\n",
    "        x = torch.cat([q,k],axis=2)\n",
    "        x_rotate_half = rotate_half(x)\n",
    "        B, L, H, D = x.shape\n",
    "        assert (D % 32 == 0) or (D % 64 == 0) or (D % 128 == 0)\n",
    "        BLOCK_H = triton.next_power_of_2(H)\n",
    "        x_embed = torch.empty_like(x)\n",
    "        num_warps=4\n",
    "        num_stages=4\n",
    "        M = B*L\n",
    "        COS_B = cos.shape[0]\n",
    "        _fused_apply_rope_fwd[(M,)](x, x_rotate_half, x_embed, cos, sin,\n",
    "                        *x.stride(),\n",
    "                        *cos.stride(),\n",
    "                        B, COS_B, L, H,D, BLOCK_H,\n",
    "                        num_warps=num_warps, num_stages=num_stages\n",
    "\n",
    "        )\n",
    "        q_embed, k_embed = x_embed.split([qh, kh], dim=2)\n",
    "        q_embed = q_embed.contiguous().transpose(1,2)\n",
    "        k_embed = k_embed.contiguous().transpose(1,2)\n",
    "\n",
    "        ctx.save_for_backward(cos, sin)\n",
    "        # ctx.infos = (B, H, L, D, BLOCK_H, M, COS_B, qh, kh)\n",
    "        # ctx.num_warps = num_warps\n",
    "        # ctx.num_stages = num_stages\n",
    "        return q_embed, k_embed\n",
    "    \n",
    "    @staticmethod\n",
    "    def backward(ctx, dq_embed, dk_embed):\n",
    "        # dq_embed = dq_embed.transpose(1,2)\n",
    "        # dk_embed = dk_embed.transpose(1,2)\n",
    "        \n",
    "        # B, H, L, D, BLOCK_H, M, COS_B, qh, kh = ctx.infos\n",
    "\n",
    "        # cos,sin = ctx.saved_tensors\n",
    "        # sin = rotate_half_v2(sin)\n",
    "        # dx_embed = torch.cat([dq_embed, dk_embed], axis=-2)\n",
    "        # dx_embed_r = rotate_half_v3(dx_embed)\n",
    "        # dx = torch.empty_like(dx_embed)\n",
    "        \n",
    "        # _fused_apply_rope_bwd[(M,)](dx_embed, dx_embed_r, dx, cos, sin,\n",
    "        #                 *dx_embed.stride(),\n",
    "        #                 *cos.stride(),\n",
    "        #                 B, COS_B, L, H,D, BLOCK_H,\n",
    "        #                 num_warps=ctx.num_warps, num_stages=ctx.num_stages\n",
    "\n",
    "        # )\n",
    "        # dq, dk = dx.split([qh, kh], dim=2)\n",
    "        # dq = dq.transpose(1,2).contiguous()\n",
    "        # dk = dk.transpose(1,2).contiguous()\n",
    "\n",
    "\n",
    "        cos,sin = ctx.saved_tensors\n",
    "        cos = cos.unsqueeze(1)\n",
    "        sin = sin.unsqueeze(1)\n",
    "        dq = dq_embed * cos + rotate_half_v2(sin) * rotate_half_v3(dq_embed)\n",
    "        dk = dk_embed * cos + rotate_half_v2(sin) * rotate_half_v3(dk_embed)\n",
    "        return dq, dk, None, None\n",
    "\n",
    "\n",
    "fused_apply_rope = _FusedApplyRope.apply\n",
    "\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# v2"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 7,
   "metadata": {},
   "outputs": [],
   "source": [
    "\n",
    "def rotate_half(x):\n",
    "    \"\"\"Rotates half the hidden dims of the input.\"\"\"\n",
    "    x1 = x[..., : x.shape[-1] // 2]\n",
    "    x2 = x[..., x.shape[-1] // 2 :]\n",
    "    return torch.cat((-x2, x1), dim=-1)\n",
    "\n",
    "def repeat_kv(hidden_states: torch.Tensor, n_rep: int) -> torch.Tensor:\n",
    "    \"\"\"\n",
    "    This is the equivalent of torch.repeat_interleave(x, dim=1, repeats=n_rep). The hidden states go from (batch,\n",
    "    num_key_value_heads, seqlen, head_dim) to (batch, num_attention_heads, seqlen, head_dim)\n",
    "    \"\"\"\n",
    "    batch, num_key_value_heads, slen, head_dim = hidden_states.shape\n",
    "    if n_rep == 1:\n",
    "        return hidden_states\n",
    "    hidden_states = hidden_states[:, :, None, :, :].expand(batch, num_key_value_heads, n_rep, slen, head_dim)\n",
    "    return hidden_states.reshape(batch, num_key_value_heads * n_rep, slen, head_dim)\n",
    "\n",
    "def rotate_half_v2(x):\n",
    "    \"\"\"Rotates half the hidden dims of the input.\"\"\"\n",
    "    x1 = x[..., : x.shape[-1] // 2]\n",
    "    x2 = x[..., x.shape[-1] // 2 :]\n",
    "    return torch.cat((x2, -x1), dim=-1)\n",
    "\n",
    "def rotate_half_v3(x):\n",
    "    \"\"\"Rotates half the hidden dims of the input.\"\"\"\n",
    "    x1 = x[..., : x.shape[-1] // 2]\n",
    "    x2 = x[..., x.shape[-1] // 2 :]\n",
    "    return torch.cat((x2, x1), dim=-1)\n",
    "\n",
    "def apply_rotary_pos_emb(q, k, cos, sin, position_ids=None, unsqueeze_dim=1):\n",
    "    \"\"\"Applies Rotary Position Embedding to the query and key tensors.\n",
    "\n",
    "    Args:\n",
    "        q (`torch.Tensor`): The query tensor.\n",
    "        k (`torch.Tensor`): The key tensor.\n",
    "        cos (`torch.Tensor`): The cosine part of the rotary embedding.\n",
    "        sin (`torch.Tensor`): The sine part of the rotary embedding.\n",
    "        position_ids (`torch.Tensor`, *optional*):\n",
    "            Deprecated and unused.\n",
    "        unsqueeze_dim (`int`, *optional*, defaults to 1):\n",
    "            The 'unsqueeze_dim' argument specifies the dimension along which to unsqueeze cos[position_ids] and\n",
    "            sin[position_ids] so that they can be properly broadcasted to the dimensions of q and k. For example, note\n",
    "            that cos[position_ids] and sin[position_ids] have the shape [batch_size, seq_len, head_dim]. Then, if q and\n",
    "            k have the shape [batch_size, heads, seq_len, head_dim], then setting unsqueeze_dim=1 makes\n",
    "            cos[position_ids] and sin[position_ids] broadcastable to the shapes of q and k. Similarly, if q and k have\n",
    "            the shape [batch_size, seq_len, heads, head_dim], then set unsqueeze_dim=2.\n",
    "    Returns:\n",
    "        `tuple(torch.Tensor)` comprising of the query and key tensors rotated using the Rotary Position Embedding.\n",
    "    \"\"\"\n",
    "    cos = cos.unsqueeze(unsqueeze_dim)\n",
    "    sin = sin.unsqueeze(unsqueeze_dim)\n",
    "    q_embed = (q * cos) + (rotate_half(q) * sin)\n",
    "    k_embed = (k * cos) + (rotate_half(k) * sin)\n",
    "    return q_embed, k_embed\n",
    "\n",
    "@triton.jit\n",
    "def _fused_apply_rope_fwd(Q, K, QR, KR, COS, SIN,\n",
    "                          Q_EMBED, K_EMBED,\n",
    "                          stride_qb, stride_qh, stride_ql, stride_qd,\n",
    "                          stride_qrb, stride_qrh, stride_qrl, stride_qrd,\n",
    "                          stride_cb, stride_cl, stride_cd,\n",
    "                          B, COS_B, L, QH, KH, D:tl.constexpr\n",
    "                          ):\n",
    "    pid = tl.program_id(0)\n",
    "    off_b = pid // L\n",
    "    off_l = pid % L\n",
    "    q_offset = pid * stride_ql\n",
    "    qr_offset = off_b * stride_qrb + off_l * stride_qrl\n",
    "    factor = QH // KH\n",
    "    k_offset = pid * stride_ql // factor\n",
    "    kr_offset = off_b * stride_qrb // factor + off_l * stride_qrl\n",
    "    if B == COS_B:\n",
    "        cos_offset = pid * stride_cl\n",
    "    else:\n",
    "        cos_offset = off_l * stride_cl\n",
    "    # cos_offset = pid * stride_cl\n",
    "    Q += q_offset\n",
    "    QR += qr_offset\n",
    "    Q_EMBED += q_offset\n",
    "    K += k_offset\n",
    "    KR += kr_offset\n",
    "    K_EMBED += k_offset\n",
    "    COS += cos_offset\n",
    "    SIN += cos_offset\n",
    "\n",
    "    cols = tl.arange(0, D)\n",
    "    q_ptrs = Q + cols\n",
    "    qr_ptrs = QR + cols\n",
    "    qembed_ptrs = Q_EMBED + cols\n",
    "    k_ptrs = K + cols\n",
    "    kr_ptrs = KR + cols\n",
    "    kembed_ptrs = K_EMBED + cols\n",
    "    cos_ptrs = COS + cols\n",
    "    sin_ptrs = SIN + cols\n",
    "\n",
    "    cos = tl.load(cos_ptrs)\n",
    "    sin = tl.load(sin_ptrs)\n",
    "\n",
    "    for idx in tl.range(0, QH):\n",
    "        offset = D*idx\n",
    "        offset_r = idx * stride_qrh\n",
    "        q = tl.load(q_ptrs + offset)\n",
    "        qr = tl.load(qr_ptrs + offset_r)\n",
    "        q_embed = q * cos + qr * sin\n",
    "        tl.store(qembed_ptrs + offset, q_embed)\n",
    "        if idx % factor == 0:\n",
    "            k = tl.load(k_ptrs + offset//factor)\n",
    "            kr = tl.load(kr_ptrs + offset_r//factor)\n",
    "            k_embed = k * cos + kr * sin\n",
    "            tl.store(kembed_ptrs + offset//factor, k_embed)\n",
    "\n",
    "    # for idx in tl.range(0, KH):\n",
    "    #     offset = D*idx\n",
    "    #     offset_r = idx * stride_qrh\n",
    "    #     k = tl.load(k_ptrs + offset)\n",
    "    #     kr = tl.load(kr_ptrs + offset_r)\n",
    "    #     k_embed = k * cos + kr * sin\n",
    "    #     tl.store(kembed_ptrs + offset, k_embed)\n",
    "\n",
    "@triton.jit\n",
    "def _fused_apply_rope_bwd(DQ_EMBED, DK_EMBED, DQ_EMBED_R, DK_EMBED_R, COS, SIN,\n",
    "                          DQ, DK, \n",
    "                        stride_qb, stride_qh, stride_ql, stride_qd,\n",
    "                        stride_kb, stride_kh, stride_kl, stride_kd,\n",
    "                        stride_cb, stride_cl, stride_cd,\n",
    "                        B, COS_B, L, QH, KH, D: tl.constexpr,\n",
    "                        ):\n",
    "    pid = tl.program_id(0)\n",
    "    off_b = pid // L\n",
    "    off_l = pid % L\n",
    "    factor = QH // KH\n",
    "    dq_offset = off_b * stride_qb + stride_ql * off_l\n",
    "    dk_offset = off_b * stride_kb  + stride_kl * off_l\n",
    "    if B == COS_B:\n",
    "        cos_offset = pid * stride_cl\n",
    "    else:\n",
    "        cos_offset = off_l * stride_cl\n",
    "    # cos_offset = pid * stride_cl\n",
    "    DQ += dq_offset\n",
    "    DQ_EMBED += dq_offset\n",
    "    DQ_EMBED_R += dq_offset\n",
    "    DK += dk_offset\n",
    "    DK_EMBED += dk_offset\n",
    "    DK_EMBED_R += dk_offset\n",
    "    COS += cos_offset\n",
    "    SIN += cos_offset\n",
    "\n",
    "    cols = tl.arange(0, D)\n",
    "    dq_ptrs = DQ + cols\n",
    "    dq_embed_ptrs = DQ_EMBED + cols\n",
    "    dq_embed_r_ptrs = DQ_EMBED_R + cols\n",
    "    dk_ptrs = DK + cols\n",
    "    dk_embed_ptrs = DK_EMBED + cols\n",
    "    dk_embed_r_ptrs = DK_EMBED_R + cols\n",
    "    cos_ptrs = COS + cols\n",
    "    sin_ptrs = SIN + cols\n",
    "\n",
    "    cos = tl.load(cos_ptrs)\n",
    "    sin = tl.load(sin_ptrs)\n",
    "\n",
    "    for idx in tl.range(0, QH):\n",
    "        offset = stride_qh * idx\n",
    "        dq_embed = tl.load(dq_embed_ptrs + offset)\n",
    "        dq_embed_r = tl.load(dq_embed_r_ptrs + offset)\n",
    "        dq = dq_embed * cos + dq_embed_r * sin\n",
    "        tl.store(dq_ptrs + offset, dq)\n",
    "        if idx % factor == 0:\n",
    "            offset = idx * stride_kh // factor\n",
    "            dk_embed = tl.load(dk_embed_ptrs + offset)\n",
    "            dk_embed_r = tl.load(dk_embed_r_ptrs + offset)\n",
    "            dk = dk_embed * cos + dk_embed_r * sin\n",
    "            tl.store(dk_ptrs + offset, dk)\n",
    "\n",
    "    # for idx in tl.range(0, KH):\n",
    "    #     offset = idx * stride_kh\n",
    "    #     dk_embed = tl.load(dk_embed_ptrs + offset)\n",
    "    #     dk_embed_r = tl.load(dk_embed_r_ptrs + offset)\n",
    "    #     dk = dk_embed * cos + dk_embed_r * sin\n",
    "    #     tl.store(dk_ptrs + offset, dk)\n",
    "\n",
    "class _FusedApplyRope(torch.autograd.Function):\n",
    "    @staticmethod\n",
    "    def forward(ctx, q, k, cos, sin):\n",
    "        assert q.transpose(1,2).is_contiguous()\n",
    "        # print(q.stride(), k.stride())\n",
    "        B, QH, L, D = q.shape\n",
    "        KH = k.size(1)\n",
    "        assert (D % 32 == 0) or (D % 64 == 0) or (D % 128 == 0)\n",
    "        num_stages=4\n",
    "        num_warps=1\n",
    "\n",
    "        qr = rotate_half(q)\n",
    "        kr = rotate_half(k)\n",
    "        # print(qr.stride())\n",
    "        q_embed = torch.empty(B, L, QH, D, device=q.device, dtype=k.dtype)\n",
    "        k_embed = torch.empty(B, L, KH, D, device=q.device, dtype=k.dtype)\n",
    "        M = B*L\n",
    "        COS_B = cos.shape[0]\n",
    "        _fused_apply_rope_fwd[(M,)](q,k,qr,kr,cos, sin,\n",
    "                                    q_embed, k_embed,\n",
    "                                    *q.stride(),\n",
    "                                    *qr.stride(),\n",
    "                                    *cos.stride(),\n",
    "                                    B, COS_B, L, QH, KH, D,\n",
    "                                    num_warps=num_warps, num_stages=num_stages\n",
    "\n",
    "        )\n",
    "\n",
    "        ctx.save_for_backward(cos, sin)\n",
    "        ctx.infos = (B, QH, KH, L, D, M, COS_B)\n",
    "        ctx.num_warps = num_warps\n",
    "        ctx.num_stages = num_stages\n",
    "        return q_embed.transpose(1,2), k_embed.transpose(1,2)\n",
    "\n",
    "    @staticmethod\n",
    "    def backward(ctx, dq_embed, dk_embed):\n",
    "        # print(dq_embed.shape, dq_embed.stride())\n",
    "        B, QH, KH, L, D, M, COS_B = ctx.infos\n",
    "        cos,sin = ctx.saved_tensors\n",
    "        dq = torch.empty_like(dq_embed)\n",
    "        dk = torch.empty_like(dk_embed)\n",
    "        dq_embed_r = rotate_half_v3(dq_embed)\n",
    "        dk_embed_r = rotate_half_v3(dk_embed)\n",
    "        sin = rotate_half_v2(sin)\n",
    "        _fused_apply_rope_bwd[(M,)](dq_embed, dk_embed, dq_embed_r, dk_embed_r, cos, sin,\n",
    "                                    dq, dk,\n",
    "                                    *dq.stride(),\n",
    "                                    *dk.stride(),\n",
    "                                    *cos.stride(),\n",
    "                                    B, COS_B, L, QH, KH, D,\n",
    "                                    num_warps=ctx.num_warps, num_stages=ctx.num_stages\n",
    "                                    )\n",
    "        print(dq.stride(), dq_embed.stride(), dq_embed_r.stride())\n",
    "        print(dk.stride(), dk_embed.stride(), dk_embed_r.stride())\n",
    "        return dq, dk, None, None\n",
    "\n",
    "\n",
    "fused_apply_rope = _FusedApplyRope.apply\n",
    "\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# v3"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 9,
   "metadata": {},
   "outputs": [],
   "source": [
    "@triton.jit\n",
    "def _fused_apply_rope_fwd(Q, K, QR, KR, COS, SIN,\n",
    "                          Q_EMBED, K_EMBED,\n",
    "                          stride_qb, stride_qh, stride_ql, stride_qd,\n",
    "                          stride_qrb, stride_qrh, stride_qrl, stride_qrd,\n",
    "                          stride_cb, stride_cl, stride_cd,\n",
    "                          B, COS_B, L, QH, KH, D:tl.constexpr,BLOCK_QH:tl.constexpr, BLOCK_KH:tl.constexpr,\n",
    "                          ):\n",
    "    pid = tl.program_id(0)\n",
    "    off_b = pid // L\n",
    "    off_l = pid % L\n",
    "    q_offset = pid * stride_ql\n",
    "    qr_offset = off_b * stride_qrb + off_l * stride_qrl\n",
    "    factor = QH // KH\n",
    "    k_offset = pid * stride_ql // factor\n",
    "    kr_offset = off_b * stride_qrb // factor + off_l * stride_qrl\n",
    "    if B == COS_B:\n",
    "        cos_offset = pid * stride_cl\n",
    "    else:\n",
    "        cos_offset = off_l * stride_cl\n",
    "    # cos_offset = pid * stride_cl\n",
    "    Q += q_offset\n",
    "    QR += qr_offset\n",
    "    Q_EMBED += q_offset\n",
    "    K += k_offset\n",
    "    KR += kr_offset\n",
    "    K_EMBED += k_offset\n",
    "    COS += cos_offset\n",
    "    SIN += cos_offset\n",
    "    q_block_ptrs = tl.make_block_ptr(\n",
    "        base=Q,\n",
    "        shape=(QH, D),\n",
    "        strides=(stride_qh, stride_qd),\n",
    "        offsets=(0,0),\n",
    "        block_shape=(BLOCK_QH, D),\n",
    "        order=(1,0)\n",
    "    )\n",
    "    qr_block_ptrs = tl.make_block_ptr(\n",
    "        base=QR,\n",
    "        shape=(QH, D),\n",
    "        strides=(stride_qrh, stride_qrd),\n",
    "        offsets=(0,0),\n",
    "        block_shape=(BLOCK_QH, D),\n",
    "        order=(1,0)\n",
    "    )\n",
    "    qembed_block_ptrs = tl.make_block_ptr(\n",
    "        base=Q_EMBED,\n",
    "        shape=(QH, D),\n",
    "        strides=(stride_qh, stride_qd),\n",
    "        offsets=(0,0),\n",
    "        block_shape=(BLOCK_QH, D),\n",
    "        order=(1,0)\n",
    "    )\n",
    "    k_block_ptrs = tl.make_block_ptr(\n",
    "        base=K,\n",
    "        shape=(KH, D),\n",
    "        strides=(stride_qh, stride_qd),\n",
    "        offsets=(0,0),\n",
    "        block_shape=(BLOCK_KH, D),\n",
    "        order=(1,0)\n",
    "    )\n",
    "    kr_block_ptrs = tl.make_block_ptr(\n",
    "        base=KR,\n",
    "        shape=(KH, D),\n",
    "        strides=(stride_qrh, stride_qrd),\n",
    "        offsets=(0,0),\n",
    "        block_shape=(BLOCK_KH, D),\n",
    "        order=(1,0)\n",
    "    )\n",
    "    kembed_block_ptrs = tl.make_block_ptr(\n",
    "        base=K_EMBED,\n",
    "        shape=(KH, D),\n",
    "        strides=(stride_qh, stride_qd),\n",
    "        offsets=(0,0),\n",
    "        block_shape=(BLOCK_KH, D),\n",
    "        order=(1,0)\n",
    "    )\n",
    "\n",
    "    cols = tl.arange(0, D)\n",
    "    cos_ptrs = COS + cols\n",
    "    sin_ptrs = SIN + cols\n",
    "    cos = tl.load(cos_ptrs)\n",
    "    dtype = cos.dtype\n",
    "    cos = cos.to(tl.float32)\n",
    "    sin = tl.load(sin_ptrs).to(tl.float32)\n",
    "\n",
    "    q = tl.load(q_block_ptrs, boundary_check=(0,)).to(tl.float32)\n",
    "    qr = tl.load(qr_block_ptrs, boundary_check=(0,)).to(tl.float32)\n",
    "    q_embed = q * cos + qr * sin\n",
    "    tl.store(qembed_block_ptrs, q_embed.to(dtype), boundary_check=(0,))\n",
    "    k = tl.load(k_block_ptrs, boundary_check=(0,)).to(tl.float32)\n",
    "    kr = tl.load(kr_block_ptrs, boundary_check=(0,)).to(tl.float32)\n",
    "    k_embed = k * cos + kr * sin\n",
    "    tl.store(kembed_block_ptrs, k_embed.to(dtype), boundary_check=(0,))\n",
    "\n",
    "\n",
    "@triton.jit\n",
    "def _fused_apply_rope_bwd(DQ_EMBED, DK_EMBED, DQ_EMBED_R, DK_EMBED_R, COS, SIN,\n",
    "                          DQ, DK, \n",
    "                        stride_qb, stride_qh, stride_ql, stride_qd,\n",
    "                        stride_qrb, stride_qrh, stride_qrl, stride_qrd,\n",
    "                        stride_kb, stride_kh, stride_kl, stride_kd,\n",
    "                        stride_krb, stride_krh, stride_krl, stride_krd,\n",
    "                        stride_cb, stride_cl, stride_cd,\n",
    "                        B, COS_B, L, QH, KH, D: tl.constexpr, BLOCK_QH:tl.constexpr, BLOCK_KH:tl.constexpr,\n",
    "                        ):\n",
    "    pid = tl.program_id(0)\n",
    "    off_b = pid // L\n",
    "    off_l = pid % L\n",
    "    dq_offset = off_b * stride_qb + stride_ql * off_l\n",
    "    dqr_offset = off_b * stride_qrb + stride_qrl * off_l\n",
    "    dk_offset = off_b * stride_kb  + stride_kl * off_l\n",
    "    dkr_offset = off_b * stride_krb  + stride_krl * off_l\n",
    "    if B == COS_B:\n",
    "        cos_offset = pid * stride_cl\n",
    "    else:\n",
    "        cos_offset = off_l * stride_cl\n",
    "    # cos_offset = pid * stride_cl\n",
    "    DQ += dq_offset\n",
    "    DQ_EMBED += dq_offset\n",
    "    DQ_EMBED_R += dqr_offset\n",
    "    DK += dk_offset\n",
    "    DK_EMBED += dk_offset\n",
    "    DK_EMBED_R += dkr_offset\n",
    "    COS += cos_offset\n",
    "    SIN += cos_offset\n",
    "\n",
    "    dq_block_ptrs = tl.make_block_ptr(\n",
    "        base=DQ,\n",
    "        shape=(QH, D),\n",
    "        strides=(stride_qh, stride_qd),\n",
    "        offsets=(0,0),\n",
    "        block_shape=(BLOCK_QH, D),\n",
    "        order=(1,0)\n",
    "    )\n",
    "    dq_embed_block_ptrs = tl.make_block_ptr(\n",
    "        base=DQ_EMBED,\n",
    "        shape=(QH, D),\n",
    "        strides=(stride_qh, stride_qd),\n",
    "        offsets=(0,0),\n",
    "        block_shape=(BLOCK_QH, D),\n",
    "        order=(1,0)\n",
    "    )\n",
    "    dq_embed_r_block_ptrs = tl.make_block_ptr(\n",
    "        base=DQ_EMBED_R,\n",
    "        shape=(QH, D),\n",
    "        strides=(stride_qrh, stride_qrd),\n",
    "        offsets=(0,0),\n",
    "        block_shape=(BLOCK_QH, D),\n",
    "        order=(1,0)\n",
    "    )\n",
    "    dk_block_ptrs = tl.make_block_ptr(\n",
    "        base=DK,\n",
    "        shape=(KH, D),\n",
    "        strides=(stride_kh, stride_kd),\n",
    "        offsets=(0,0),\n",
    "        block_shape=(BLOCK_KH, D),\n",
    "        order=(1,0)\n",
    "    )\n",
    "    dk_embed_block_ptrs = tl.make_block_ptr(\n",
    "        base=DK_EMBED,\n",
    "        shape=(KH, D),\n",
    "        strides=(stride_kh, stride_kd),\n",
    "        offsets=(0,0),\n",
    "        block_shape=(BLOCK_KH, D),\n",
    "        order=(1,0)\n",
    "    )\n",
    "    dk_embed_r_block_ptrs = tl.make_block_ptr(\n",
    "        base=DK_EMBED_R,\n",
    "        shape=(KH, D),\n",
    "        strides=(stride_krh, stride_krd),\n",
    "        offsets=(0,0),\n",
    "        block_shape=(BLOCK_KH, D),\n",
    "        order=(1,0)\n",
    "    )\n",
    "    cols = tl.arange(0, D)\n",
    "    cos_ptrs = COS + cols\n",
    "    sin_ptrs = SIN + cols\n",
    "\n",
    "    cos = tl.load(cos_ptrs)\n",
    "    dtype = cos.dtype\n",
    "    cos = cos.to(tl.float32)\n",
    "    sin = tl.load(sin_ptrs).to(tl.float32)\n",
    "\n",
    "    dq_embed = tl.load(dq_embed_block_ptrs, boundary_check=(0,)).to(tl.float32)\n",
    "    dq_embed_r = tl.load(dq_embed_r_block_ptrs, boundary_check=(0,)).to(tl.float32)\n",
    "    dq = dq_embed * cos + dq_embed_r * sin\n",
    "    tl.store(dq_block_ptrs, dq.to(dtype), boundary_check=(0,))\n",
    "    dk_embed = tl.load(dk_embed_block_ptrs, boundary_check=(0,)).to(tl.float32)\n",
    "    dk_embed_r = tl.load(dk_embed_r_block_ptrs, boundary_check=(0,)).to(tl.float32)\n",
    "    dk = dk_embed * cos + dk_embed_r * sin\n",
    "    tl.store(dk_block_ptrs, dk.to(dtype), boundary_check=(0,))\n",
    "\n",
    "class _FusedApplyRope(torch.autograd.Function):\n",
    "    @staticmethod\n",
    "    def forward(ctx, q, k, cos, sin):\n",
    "        assert q.transpose(1,2).is_contiguous()\n",
    "        # print(q.stride(), k.stride())\n",
    "        B, QH, L, D = q.shape\n",
    "        KH = k.size(1)\n",
    "        assert (D % 32 == 0) or (D % 64 == 0) or (D % 128 == 0)\n",
    "        num_stages=1\n",
    "        num_warps=8\n",
    "\n",
    "        qr = rotate_half(q)\n",
    "        kr = rotate_half(k)\n",
    "        # print(qr.stride())\n",
    "        q_embed = torch.empty(B, L, QH, D, device=q.device, dtype=k.dtype)\n",
    "        k_embed = torch.empty(B, L, KH, D, device=q.device, dtype=k.dtype)\n",
    "        M = B*L\n",
    "        COS_B = cos.shape[0]\n",
    "        BLOCK_QH = triton.next_power_of_2(QH)\n",
    "        BLOCK_KH = triton.next_power_of_2(KH)\n",
    "        _fused_apply_rope_fwd[(M,)](q,k,qr,kr,cos, sin,\n",
    "                                    q_embed, k_embed,\n",
    "                                    *q.stride(),\n",
    "                                    *qr.stride(),\n",
    "                                    *cos.stride(),\n",
    "                                    B, COS_B, L, QH, KH, D, BLOCK_QH, BLOCK_KH,\n",
    "                                    num_warps=num_warps, num_stages=num_stages\n",
    "\n",
    "        )\n",
    "\n",
    "        ctx.save_for_backward(cos, sin)\n",
    "        ctx.infos = (B, QH, KH, L, D, M, COS_B, BLOCK_QH, BLOCK_KH)\n",
    "        ctx.num_warps = num_warps\n",
    "        ctx.num_stages = num_stages\n",
    "        return q_embed.transpose(1,2), k_embed.transpose(1,2)\n",
    "\n",
    "    @staticmethod\n",
    "    def backward(ctx, dq_embed, dk_embed):\n",
    "        B, QH, KH, L, D, M, COS_B ,BLOCK_QH, BLOCK_KH= ctx.infos\n",
    "        cos,sin = ctx.saved_tensors\n",
    "        dq = torch.empty_like(dq_embed)\n",
    "        dk = torch.empty_like(dk_embed)\n",
    "        dq_embed_r = rotate_half_v3(dq_embed)\n",
    "        dk_embed_r = rotate_half_v3(dk_embed)\n",
    "        sin = rotate_half_v2(sin)\n",
    "        _fused_apply_rope_bwd[(M,)](dq_embed, dk_embed, dq_embed_r, dk_embed_r, cos, sin,\n",
    "                                    dq, dk,\n",
    "                                    *dq.stride(),\n",
    "                                    *dq_embed_r.stride(),\n",
    "                                    *dk.stride(),\n",
    "                                    *dk_embed_r.stride(),\n",
    "                                    *cos.stride(),\n",
    "                                    B, COS_B, L, QH, KH, D, BLOCK_QH, BLOCK_KH,\n",
    "                                    num_warps=ctx.num_warps, num_stages=ctx.num_stages\n",
    "                                    )\n",
    "        # 不同模型传回来的stride都不一样，有的dk_embed是连续的，有的不连续\n",
    "        # 因此跟forward不太一样，需要把所有stride都传进去\n",
    "        return dq, dk, None, None\n",
    "\n",
    "fused_apply_rope = _FusedApplyRope.apply"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# unsloth"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 21,
   "metadata": {},
   "outputs": [],
   "source": [
    "ROPE_GROUP_SIZE = 4\n",
    "@triton.heuristics({\"BACKWARD_PASS\": lambda args: bool(args[\"BACKWARD_PASS\"]),})\n",
    "@triton.jit\n",
    "def _rope_embedding(\n",
    "    Q,     Q_row_stride,\n",
    "    cos, cos_row_stride,\n",
    "    sin, sin_row_stride,\n",
    "    seqlen,\n",
    "    head_dim      : tl.constexpr,\n",
    "    n_heads       : tl.constexpr,\n",
    "    BACKWARD_PASS : tl.constexpr,\n",
    "    BLOCK_SIZE    : tl.constexpr,\n",
    "):\n",
    "    \"\"\"\n",
    "        Calculates the RoPE Embedding quickly\n",
    "        RoPE is Q * cos + rotate_half(Q) * sin\n",
    "        See our blog post for more info\n",
    "    \"\"\"\n",
    "    ROPE_GROUP_SIZE = 4\n",
    "    row_position  = tl.program_id(0)\n",
    "    group_head_position = tl.program_id(1)\n",
    "    col_offsets  = tl.arange(0, BLOCK_SIZE)\n",
    "    half_head_dim = head_dim // 2\n",
    "    mask = col_offsets < half_head_dim\n",
    "\n",
    "    sin1 = tl.load(sin + (row_position % seqlen)*sin_row_stride + \\\n",
    "                   half_head_dim*0 + col_offsets, mask = mask, other = 0)\n",
    "    cos1 = tl.load(cos + (row_position % seqlen)*cos_row_stride + \\\n",
    "                   half_head_dim*0 + col_offsets, mask = mask, other = 0)\n",
    "\n",
    "    if BACKWARD_PASS:\n",
    "        # See our blog post for more info.\n",
    "        sin1 = -sin1\n",
    "    pass\n",
    "\n",
    "    # [TODO] Autotune ROPE_GROUP_SIZE to be 1, 2, 4, 8\n",
    "    head_start = group_head_position * ROPE_GROUP_SIZE\n",
    "    head_end = min((head_start + ROPE_GROUP_SIZE), n_heads)\n",
    "\n",
    "    # 10% Faster kernel from [HuyNguyen-hust](https://github.com/unslothai/unsloth/pull/238)\n",
    "    for k in range(head_start, head_end):\n",
    "        offs_q1 = row_position * Q_row_stride + k * head_dim + col_offsets\n",
    "        offs_q2 = row_position * Q_row_stride + k * head_dim + col_offsets + half_head_dim\n",
    "\n",
    "        # For Gemma - sometimes RoPE must be done in float32 and not bfloat16\n",
    "        Q1 = tl.load(Q + offs_q1, mask = mask, other = 0).to(sin1.dtype)\n",
    "        Q2 = tl.load(Q + offs_q2, mask = mask, other = 0).to(sin1.dtype)\n",
    "\n",
    "        tl.store(Q + offs_q1, Q1*cos1 - Q2*sin1, mask = mask)\n",
    "        tl.store(Q + offs_q2, Q2*cos1 + Q1*sin1, mask = mask)\n",
    "    pass\n",
    "pass\n",
    "\n",
    "\n",
    "class Fast_RoPE_Embedding(torch.autograd.Function):\n",
    "    @staticmethod\n",
    "    def forward(ctx, Q, cos, sin):\n",
    "        cos, sin = cos.squeeze(), sin.squeeze()\n",
    "        batch    : int\n",
    "        seq_len  : int\n",
    "        n_heads  : int\n",
    "        head_dim : int\n",
    "        batch, seq_len, n_heads, head_dim = Q.shape\n",
    "        Q = Q.view(batch*seq_len, n_heads*head_dim)\n",
    "        n_rows : int\n",
    "        n_cols : int\n",
    "        n_rows, n_cols = Q.shape\n",
    "        assert(seq_len <= cos.shape[0])\n",
    "\n",
    "        # [TODO] Changing blocksize to head_dim//2 seems to have\n",
    "        # some concurrency / un-deterministic issues.\n",
    "        BLOCK_SIZE, num_warps = head_dim//2, 8 # (head_dim//2)\n",
    "        \n",
    "        # group_size = 4 # 4 or 8, too large group_size can hurt performance.\n",
    "        div : int\n",
    "        mod : int\n",
    "        div, mod = divmod(n_heads, ROPE_GROUP_SIZE)\n",
    "        n_groups : int = div + (mod != 0)\n",
    "\n",
    "        _rope_embedding[(n_rows, n_groups, )](\n",
    "              Q,   Q.stride(0),\n",
    "            cos, cos.stride(0),\n",
    "            sin, sin.stride(0),\n",
    "            seq_len,\n",
    "            head_dim, n_heads,\n",
    "            BACKWARD_PASS = False,\n",
    "            BLOCK_SIZE = BLOCK_SIZE,\n",
    "            num_warps  = num_warps,\n",
    "        )\n",
    "        ctx.BLOCK_SIZE = BLOCK_SIZE\n",
    "        ctx.num_warps  = num_warps\n",
    "        ctx.n_groups = n_groups\n",
    "        ctx.cos = cos\n",
    "        ctx.sin = sin\n",
    "        return Q.view(batch, seq_len, n_heads, head_dim)\n",
    "    pass\n",
    "\n",
    "    @staticmethod\n",
    "    def backward(ctx, dY):\n",
    "        batch    : int\n",
    "        seq_len  : int\n",
    "        n_heads  : int\n",
    "        head_dim : int\n",
    "        batch, seq_len, n_heads, head_dim = dY.shape\n",
    "        dY = dY.reshape(batch*seq_len, n_heads*head_dim)\n",
    "        # Must be reshape not view\n",
    "        n_rows : int\n",
    "        n_cols : int\n",
    "        n_rows, n_cols = dY.shape\n",
    "\n",
    "        cos = ctx.cos\n",
    "        sin = ctx.sin\n",
    "\n",
    "        _rope_embedding[(n_rows, ctx.n_groups, )](\n",
    "            dY,  dY .stride(0),\n",
    "            cos, cos.stride(0),\n",
    "            sin, sin.stride(0),\n",
    "            seq_len, head_dim, n_heads,\n",
    "            BACKWARD_PASS = True,\n",
    "            BLOCK_SIZE = ctx.BLOCK_SIZE,\n",
    "            num_warps  = ctx.num_warps,\n",
    "        )\n",
    "        dY = dY.view(batch, seq_len, n_heads, head_dim)\n",
    "        return dY, None, None,\n",
    "    pass\n",
    "pass\n",
    "\n",
    "# [TODO] Unsure why RoPE Embedding is not torch.compiling properly\n",
    "@torch.compiler.disable\n",
    "def fast_rope_embedding(Q, K, cos, sin):\n",
    "    Q = Fast_RoPE_Embedding.apply(Q.transpose(1, 2), cos, sin).transpose(1, 2)\n",
    "    K = Fast_RoPE_Embedding.apply(K.transpose(1, 2), cos, sin).transpose(1, 2)\n",
    "    return Q, K\n",
    "pass"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# v4"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "- unsloth的没跑起来，貌似传参和HF中的不太一样，不过我借鉴了它实现翻转的方式，v4是在v3上进行升级，去掉外部的翻转"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 6,
   "metadata": {},
   "outputs": [],
   "source": [
    "@triton.jit\n",
    "def _fused_apply_rope_fwd(Q, K, COS, SIN,\n",
    "                          B, COS_B, L, QH, KH, D:tl.constexpr, HALF_D:tl.constexpr,\n",
    "                          BLOCK_QH:tl.constexpr, BLOCK_KH:tl.constexpr, BLOCK_D:tl.constexpr,\n",
    "                          CHUNK_N:tl.constexpr=64\n",
    "                          ):\n",
    "    start_n = tl.cast(tl.program_id(0), tl.int64) * CHUNK_N + tl.program_id(1)\n",
    "    if start_n >= B*L:\n",
    "        return\n",
    "    off_b = start_n // L\n",
    "    off_l = start_n % L\n",
    "\n",
    "    Q += start_n * QH * D\n",
    "    K += start_n * KH * D\n",
    "    COS += off_b * (COS_B // B) * L * D + off_l * D\n",
    "    SIN += off_b * (COS_B // B) * L * D + off_l * D\n",
    "\n",
    "    cols = tl.arange(0, BLOCK_D)\n",
    "    cos1 = tl.load(COS + cols, mask=cols<HALF_D, other=0.).to(tl.float32)[None, :]\n",
    "    cos2 = tl.load(COS + cols + HALF_D, mask=(cols + HALF_D)<D, other=0.).to(tl.float32)[None, :]\n",
    "    sin1 = tl.load(SIN + cols, mask=cols<HALF_D, other=0.).to(tl.float32)[None, :]\n",
    "    sin2 = tl.load(SIN + cols + HALF_D, mask=(cols + HALF_D)<D, other=0.).to(tl.float32)[None, :]\n",
    "\n",
    "    k1 = tl.load(K + tl.arange(0, BLOCK_KH)[:, None] * D + cols[None, :], \n",
    "                 mask=(cols[None, :]<HALF_D) & (tl.arange(0, BLOCK_KH)[:, None] < KH), other=0.).to(tl.float32)\n",
    "    k2 = tl.load(K + tl.arange(0, BLOCK_KH)[:, None] * D + cols[None, :] + HALF_D, \n",
    "                 mask=((HALF_D+cols[None, :])<D) & (tl.arange(0, BLOCK_KH)[:, None] < KH), other=0.).to(tl.float32)\n",
    "    k_embed1 = k1 * cos1 - k2 * sin1\n",
    "    k_embed2 = k2 * cos2 + k1 * sin2\n",
    "    tl.store(K + tl.arange(0, BLOCK_KH)[:, None] * D + cols[None, :], k_embed1, mask=(cols[None, :]<HALF_D)&(tl.arange(0, BLOCK_KH)[:, None] < KH))\n",
    "    tl.store(K + tl.arange(0, BLOCK_KH)[:, None] * D + cols[None, :] + HALF_D, k_embed2, mask=((HALF_D+cols[None, :])<D) & (tl.arange(0, BLOCK_KH)[:, None] < KH))\n",
    "    \n",
    "    q1 = tl.load(Q + tl.arange(0, BLOCK_QH)[:, None] * D + cols[None, :], \n",
    "                 mask=(cols[None, :]<HALF_D) & (tl.arange(0, BLOCK_QH)[:, None] < QH), other=0.).to(tl.float32)\n",
    "    q2 = tl.load(Q + tl.arange(0, BLOCK_QH)[:, None] * D + cols[None, :] + HALF_D, \n",
    "                 mask=((HALF_D+cols[None, :])<D) & (tl.arange(0, BLOCK_QH)[:, None] < QH), other=0.).to(tl.float32)\n",
    "    q_embed1 = q1 * cos1 - q2 * sin1\n",
    "    q_embed2 = q2 * cos2 + q1 * sin2\n",
    "    tl.store(Q + tl.arange(0, BLOCK_QH)[:, None] * D + cols[None, :], q_embed1, mask=(cols[None, :]<HALF_D)&(tl.arange(0, BLOCK_QH)[:, None] < QH))\n",
    "    tl.store(Q + tl.arange(0, BLOCK_QH)[:, None] * D + cols[None, :] + HALF_D, q_embed2, mask=((HALF_D+cols[None, :])<D) & (tl.arange(0, BLOCK_QH)[:, None] < QH))\n",
    "    \n",
    "\n",
    "@triton.jit\n",
    "def _fused_apply_rope_bwd(DQ, DK, COS, SIN,\n",
    "                          dq_stride_b, dq_stride_h, dq_stride_l, dq_stride_d,\n",
    "                          dk_stride_b, dk_stride_h, dk_stride_l, dk_stride_d,\n",
    "                        B, COS_B, L, QH, KH, D: tl.constexpr, HALF_D: tl.constexpr,\n",
    "                        BLOCK_QH:tl.constexpr, BLOCK_KH:tl.constexpr, BLOCK_D:tl.constexpr,\n",
    "                        CHUNK_N:tl.constexpr=64\n",
    "                        ):\n",
    "    start_n = tl.cast(tl.program_id(0), tl.int64) * CHUNK_N + tl.program_id(1)\n",
    "    if start_n >= B*L:\n",
    "        return\n",
    "    off_b = start_n // L\n",
    "    off_l = start_n % L\n",
    "\n",
    "    DQ += off_b * dq_stride_b + off_l * dq_stride_l\n",
    "    DK += off_b * dk_stride_b + off_l * dk_stride_l\n",
    "    COS += off_b * (COS_B // B) * L * D + off_l * D\n",
    "    SIN += off_b * (COS_B // B) * L * D + off_l * D\n",
    "\n",
    "    cols = tl.arange(0, BLOCK_D)\n",
    "    cos1 = tl.load(COS + cols, mask=cols<HALF_D, other=0.).to(tl.float32)[None, :]\n",
    "    cos2 = tl.load(COS + cols + HALF_D, mask=(cols + HALF_D)<D, other=0.).to(tl.float32)[None, :]\n",
    "    sin1 = tl.load(SIN + cols, mask=cols<HALF_D, other=0.).to(tl.float32)[None, :]\n",
    "    sin2 = tl.load(SIN + cols + HALF_D, mask=(cols + HALF_D)<D, other=0.).to(tl.float32)[None, :]\n",
    "\n",
    "    dk_embed1 = tl.load(DK+ tl.arange(0, BLOCK_KH)[:, None] * dk_stride_h + cols[None, :], \n",
    "                 mask=(cols[None, :]<HALF_D) & (tl.arange(0, BLOCK_KH)[:, None] < KH), other=0.).to(tl.float32)\n",
    "    dk_embed2 = tl.load(DK + tl.arange(0, BLOCK_KH)[:, None] * dk_stride_h + cols[None, :] + HALF_D, \n",
    "                 mask=((HALF_D+cols[None, :])<D) & (tl.arange(0, BLOCK_KH)[:, None] < KH), other=0.).to(tl.float32)\n",
    "    dk1 = dk_embed1 * cos1 + sin2 * dk_embed2\n",
    "    dk2 = dk_embed2 * cos2 - sin1 * dk_embed1\n",
    "    tl.store(DK + tl.arange(0, BLOCK_KH)[:, None] * dk_stride_h + cols[None, :], dk1, mask=(cols[None, :]<HALF_D)&(tl.arange(0, BLOCK_KH)[:, None] < KH))\n",
    "    tl.store(DK + tl.arange(0, BLOCK_KH)[:, None] * dk_stride_h + cols[None, :] + HALF_D, dk2, mask=((HALF_D+cols[None, :])<D) & (tl.arange(0, BLOCK_KH)[:, None] < KH))\n",
    "\n",
    "    dq_embed1 = tl.load(DQ + tl.arange(0, BLOCK_QH)[:, None] * dq_stride_h + cols[None, :], \n",
    "                 mask=(cols[None, :]<HALF_D) & (tl.arange(0, BLOCK_QH)[:, None] < QH), other=0.).to(tl.float32)\n",
    "    dq_embed2 = tl.load(DQ + tl.arange(0, BLOCK_QH)[:, None] * dq_stride_h + cols[None, :] + HALF_D, \n",
    "                 mask=((HALF_D+cols[None, :])<D) & (tl.arange(0, BLOCK_QH)[:, None] < QH), other=0.).to(tl.float32)\n",
    "    dq1 = dq_embed1 * cos1 + sin2 * dq_embed2\n",
    "    dq2 = dq_embed2 * cos2 - sin1 * dq_embed1\n",
    "    tl.store(DQ + tl.arange(0, BLOCK_QH)[:, None] * dq_stride_h + cols[None, :], dq1, mask=(cols[None, :]<HALF_D)&(tl.arange(0, BLOCK_QH)[:, None] < QH))\n",
    "    tl.store(DQ + tl.arange(0, BLOCK_QH)[:, None] * dq_stride_h + cols[None, :] + HALF_D, dq2, mask=((HALF_D+cols[None, :])<D) & (tl.arange(0, BLOCK_QH)[:, None] < QH))\n",
    "\n",
    "class _FusedApplyRope(torch.autograd.Function):\n",
    "    @staticmethod\n",
    "    def forward(ctx, q, k, cos, sin):\n",
    "        assert q.transpose(1,2).is_contiguous() and k.transpose(1,2).is_contiguous()\n",
    "        assert cos.is_contiguous() and sin.is_contiguous()\n",
    "        # print(q.stride(), k.stride())\n",
    "        B, QH, L, D = q.shape\n",
    "        HALF_D = D // 2\n",
    "        KH = k.size(1)\n",
    "        # assert (D % 32 == 0) or (D % 64 == 0) or (D % 128 == 0)\n",
    "        BLOCK_D = triton.next_power_of_2(HALF_D)\n",
    "        num_stages=4\n",
    "        num_warps=8\n",
    "\n",
    "        # q_embed = torch.empty(B, L, QH, D, device=q.device, dtype=k.dtype)\n",
    "        # k_embed = torch.empty(B, L, KH, D, device=q.device, dtype=k.dtype)\n",
    "        \n",
    "        N = B * L \n",
    "        COS_B = cos.shape[0]\n",
    "        BLOCK_QH = triton.next_power_of_2(QH)\n",
    "        BLOCK_KH = triton.next_power_of_2(KH)\n",
    "        grid = lambda meta: (triton.cdiv(N, meta['CHUNK_N']), meta['CHUNK_N'])\n",
    "        _fused_apply_rope_fwd[(grid)](q,k,cos, sin,\n",
    "                                    B, COS_B, L, QH, KH, D, HALF_D,\n",
    "                                    BLOCK_QH, BLOCK_KH, BLOCK_D,\n",
    "                                    num_warps=num_warps, num_stages=num_stages\n",
    "\n",
    "        )\n",
    "\n",
    "        ctx.save_for_backward(cos, sin)\n",
    "        ctx.infos = (B, QH, KH, L, D, HALF_D, N, COS_B, BLOCK_QH, BLOCK_KH, BLOCK_D)\n",
    "        ctx.num_warps = num_warps\n",
    "        ctx.num_stages = num_stages\n",
    "        return q, k\n",
    "\n",
    "    @staticmethod\n",
    "    def backward(ctx, dq, dk):\n",
    "        B, QH, KH, L, D, HALF_D, N, COS_B, BLOCK_QH, BLOCK_KH, BLOCK_D = ctx.infos\n",
    "        cos,sin = ctx.saved_tensors\n",
    "        grid = lambda meta: (triton.cdiv(N, meta['CHUNK_N']), meta['CHUNK_N'])\n",
    "        _fused_apply_rope_bwd[grid](dq, dk, cos, sin,\n",
    "                                    *dq.stride(), \n",
    "                                    *dk.stride(), \n",
    "                                    B, COS_B, L, QH, KH, D, HALF_D, BLOCK_QH, BLOCK_KH, BLOCK_D,\n",
    "                                    num_warps=ctx.num_warps, num_stages=ctx.num_stages\n",
    "                                    )\n",
    "        return dq, dk, None, None\n",
    "\n",
    "fused_apply_rope = _FusedApplyRope.apply"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# v5"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "- q,k计算分离, v5比v4 forward慢一些，backward测试有问题，估计是loss的计算方式问题"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 74,
   "metadata": {},
   "outputs": [],
   "source": [
    "@triton.jit\n",
    "def _fused_apply_rope_fwd(Q, COS, SIN,\n",
    "                          Q_EMBED,\n",
    "                          stride_qb, stride_qh, stride_ql, stride_qd,\n",
    "                          stride_cb, stride_cl, stride_cd,\n",
    "                          B, COS_B, L, H, D:tl.constexpr, HALF_D:tl.constexpr,\n",
    "                          BLOCK_H:tl.constexpr\n",
    "                          ):\n",
    "    pid = tl.program_id(0)\n",
    "    off_b = pid // L\n",
    "    off_l = pid % L\n",
    "    q_offset = pid * stride_ql\n",
    "    if B == COS_B:\n",
    "        cos_offset = pid * stride_cl\n",
    "    else:\n",
    "        cos_offset = off_l * stride_cl\n",
    "    # cos_offset = pid * stride_cl\n",
    "    Q += q_offset\n",
    "    Q_EMBED += q_offset\n",
    "    COS += cos_offset\n",
    "    SIN += cos_offset\n",
    "    q_block_ptrs = tl.make_block_ptr(\n",
    "        base=Q,\n",
    "        shape=(H, D),\n",
    "        strides=(stride_qh, stride_qd),\n",
    "        offsets=(0,0),\n",
    "        block_shape=(BLOCK_H, HALF_D),\n",
    "        order=(1,0)\n",
    "    )\n",
    "    qembed_block_ptrs = tl.make_block_ptr(\n",
    "        base=Q_EMBED,\n",
    "        shape=(H, D),\n",
    "        strides=(stride_qh, stride_qd),\n",
    "        offsets=(0,0),\n",
    "        block_shape=(BLOCK_H, HALF_D),\n",
    "        order=(1,0)\n",
    "    )\n",
    "\n",
    "    cols1 = tl.arange(0, HALF_D)\n",
    "    cols2 = tl.arange(HALF_D, D)\n",
    "    cos1 = tl.load(COS + cols1)\n",
    "    dtype = cos1.dtype\n",
    "    cos1 = cos1.to(tl.float32)\n",
    "    cos2 = tl.load(COS + cols2)\n",
    "    sin1 = tl.load(SIN + cols1)\n",
    "    sin2 = tl.load(SIN + cols2)\n",
    "\n",
    "    q1 = tl.load(q_block_ptrs, boundary_check=(0,)).to(tl.float32)\n",
    "    q_block_ptrs = tl.advance(q_block_ptrs, offsets=(0, HALF_D))\n",
    "    q2 = tl.load(q_block_ptrs, boundary_check=(0,)).to(tl.float32)\n",
    "    q_embed1 = q1 * cos1 - q2 * sin1\n",
    "    q_embed2 = q2 * cos2 + q1 * sin2\n",
    "    tl.store(qembed_block_ptrs, q_embed1.to(dtype), boundary_check=(0,))\n",
    "    qembed_block_ptrs = tl.advance(qembed_block_ptrs, offsets=(0, HALF_D))\n",
    "    tl.store(qembed_block_ptrs, q_embed2.to(dtype), boundary_check=(0,))\n",
    "    \n",
    "@triton.jit\n",
    "def _fused_apply_rope_bwd(DQ_EMBED, COS, SIN,\n",
    "                          DQ,\n",
    "                        stride_qb, stride_qh, stride_ql, stride_qd,\n",
    "                        stride_cb, stride_cl, stride_cd,\n",
    "                        B, COS_B, L, H, D: tl.constexpr, HALF_D: tl.constexpr,\n",
    "                        BLOCK_H:tl.constexpr\n",
    "                        ):\n",
    "    pid = tl.program_id(0)\n",
    "    off_b = pid // L\n",
    "    off_l = pid % L\n",
    "    dq_offset = off_b * stride_qb + stride_ql * off_l\n",
    "    if B == COS_B:\n",
    "        cos_offset = pid * stride_cl\n",
    "    else:\n",
    "        cos_offset = off_l * stride_cl\n",
    "    # cos_offset = pid * stride_cl\n",
    "    DQ += dq_offset\n",
    "    DQ_EMBED += dq_offset\n",
    "    COS += cos_offset\n",
    "    SIN += cos_offset\n",
    "\n",
    "    dq_block_ptrs = tl.make_block_ptr(\n",
    "        base=DQ,\n",
    "        shape=(H, D),\n",
    "        strides=(stride_qh, stride_qd),\n",
    "        offsets=(0,0),\n",
    "        block_shape=(BLOCK_H, HALF_D),\n",
    "        order=(1,0)\n",
    "    )\n",
    "    dq_embed_block_ptrs = tl.make_block_ptr(\n",
    "        base=DQ_EMBED,\n",
    "        shape=(H, D),\n",
    "        strides=(stride_qh, stride_qd),\n",
    "        offsets=(0,0),\n",
    "        block_shape=(BLOCK_H, HALF_D),\n",
    "        order=(1,0)\n",
    "    )\n",
    "\n",
    "    cols1 = tl.arange(0, HALF_D)\n",
    "    cols2 = tl.arange(HALF_D, D)\n",
    "    cos1 = tl.load(COS + cols1)\n",
    "    dtype = cos1.dtype\n",
    "    cos1 = cos1.to(tl.float32)\n",
    "    cos2 = tl.load(COS + cols2)\n",
    "    sin1 = tl.load(SIN + cols1)\n",
    "    sin2 = tl.load(SIN + cols2)\n",
    "\n",
    "    dq_embed1 = tl.load(dq_embed_block_ptrs, boundary_check=(0,)).to(tl.float32)\n",
    "    dq_embed_block_ptrs = tl.advance(dq_embed_block_ptrs, offsets=(0, HALF_D))\n",
    "    dq_embed2 = tl.load(dq_embed_block_ptrs, boundary_check=(0,)).to(tl.float32)\n",
    "    dq1 = dq_embed1 * cos1 + sin2 * dq_embed2\n",
    "    dq2 = dq_embed2 * cos2 - sin1 * dq_embed1\n",
    "    tl.store(dq_block_ptrs, dq1.to(dtype), boundary_check=(0,))\n",
    "    dq_block_ptrs = tl.advance(dq_block_ptrs, offsets=(0, HALF_D))\n",
    "    tl.store(dq_block_ptrs, dq2.to(dtype), boundary_check=(0,))\n",
    "\n",
    "class _FusedApplyRope(torch.autograd.Function):\n",
    "    @staticmethod\n",
    "    def forward(ctx, q, cos, sin):\n",
    "        assert q.transpose(1,2).is_contiguous()\n",
    "        # print(q.stride(), k.stride())\n",
    "        B, H, L, D = q.shape\n",
    "        HALF_D = D // 2\n",
    "        assert (D % 32 == 0) or (D % 64 == 0) or (D % 128 == 0)\n",
    "        num_stages=4\n",
    "        num_warps=8\n",
    "\n",
    "        # print(qr.stride())\n",
    "        q_embed = torch.empty(B, L, H, D, device=q.device, dtype=q.dtype)\n",
    "        M = B*L\n",
    "        COS_B = cos.shape[0]\n",
    "        BLOCK_H = triton.next_power_of_2(H)\n",
    "        _fused_apply_rope_fwd[(M,)](q,cos, sin,\n",
    "                                    q_embed,\n",
    "                                    *q.stride(),\n",
    "                                    *cos.stride(),\n",
    "                                    B, COS_B, L, H, D, HALF_D,\n",
    "                                    BLOCK_H,\n",
    "                                    num_warps=num_warps, num_stages=num_stages\n",
    "\n",
    "        )\n",
    "\n",
    "        ctx.save_for_backward(cos, sin)\n",
    "        ctx.infos = (B, L, H, D, HALF_D, M, COS_B, BLOCK_H)\n",
    "        ctx.num_warps = num_warps\n",
    "        ctx.num_stages = num_stages\n",
    "        return q_embed.transpose(1,2)\n",
    "\n",
    "    @staticmethod\n",
    "    def backward(ctx, dq_embed):\n",
    "        B, L, H, D, HALF_D, M, COS_B, BLOCK_H = ctx.infos\n",
    "        cos,sin = ctx.saved_tensors\n",
    "        dq = torch.empty_like(dq_embed)\n",
    "        _fused_apply_rope_bwd[(M,)](dq_embed, cos, sin,\n",
    "                                    dq,\n",
    "                                    *dq.stride(),\n",
    "                                    *cos.stride(),\n",
    "                                    B, COS_B, L, H, D, HALF_D, BLOCK_H,\n",
    "                                    num_warps=ctx.num_warps, num_stages=ctx.num_stages\n",
    "                                    )\n",
    "        return dq, None, None\n",
    "\n",
    "_fused_apply_rope = _FusedApplyRope.apply\n",
    "def fused_apply_rope(q, k, cos, sin):\n",
    "    return _fused_apply_rope(q,cos,sin), _fused_apply_rope(k, cos, sin)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# 精度测试"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 9,
   "metadata": {},
   "outputs": [],
   "source": [
    "device = 'cuda'\n",
    "dtype = torch.float32\n",
    "bs, seq_len, num_q_head, num_k_head, head_dim = 4, 128, 32, 1, 192\n",
    "q1 = torch.randn(bs, seq_len, num_q_head, head_dim, device=device, dtype=dtype).transpose(1,2)\n",
    "q1.requires_grad_(True)\n",
    "k1  = torch.randn(bs, seq_len, num_k_head, head_dim,device=device, dtype=dtype).transpose(1,2)\n",
    "k1.requires_grad_(True)\n",
    "q2 = torch.randn(bs, seq_len, num_q_head, head_dim, device=device, dtype=dtype).transpose(1,2)\n",
    "q2.data.copy_(q1.data)\n",
    "q2.requires_grad_(True)\n",
    "k2  = torch.randn(bs, seq_len, num_k_head, head_dim, device=device, dtype=dtype).transpose(1,2)\n",
    "k2.data.copy_(k1.data)\n",
    "k2.requires_grad_(True)\n",
    "cos = torch.randn(bs, seq_len, head_dim, device=device, dtype=dtype)\n",
    "sin = torch.randn_like(cos)\n",
    "\n",
    "dy = torch.rand_like(q1)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 10,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "(786432, 192, 6144, 1)\n",
      "(786432, 192, 6144, 1)\n",
      "(786432, 192, 6144, 1)\n",
      "(786432, 192, 6144, 1)\n",
      "True True\n",
      "True True\n"
     ]
    }
   ],
   "source": [
    "if k1.grad is not None:\n",
    "    k1.grad.zero_()\n",
    "    q1.grad.zero_()\n",
    "q_embed1, k_embed1 = apply_rotary_pos_emb(q1, k1, cos, sin)\n",
    "print(q_embed1.stride())\n",
    "loss = (q_embed1 * repeat_kv(k_embed1, num_q_head//num_k_head))\n",
    "print(loss.stride())\n",
    "loss.sum().backward()\n",
    "\n",
    "if k2.grad is not None:\n",
    "    k2.grad.zero_()\n",
    "    q2.grad.zero_()\n",
    "q_embed2, k_embed2 = fused_apply_rope(q2, k2, cos, sin)\n",
    "print(q_embed2.stride())\n",
    "loss = (q_embed2 * repeat_kv(k_embed2, num_q_head//num_k_head))\n",
    "print(loss.stride())\n",
    "loss.sum().backward()\n",
    "\n",
    "print(torch.allclose(q_embed1, q_embed2, atol=1e-3), torch.allclose(k_embed1, k_embed2, atol=1e-3))\n",
    "print(torch.allclose(q1.grad, q2.grad, atol=1e-3), torch.allclose(k1.grad, k2.grad, atol=1e-3))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# forward\n",
    "- v4比v5稍微快些"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 160,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "image/png": "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",
      "text/plain": [
       "<Figure size 640x480 with 1 Axes>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "forward:\n",
      "    seq_len  FusedApplyRope   ApplyRope\n",
      "0     128.0        8.906465   67.534991\n",
      "1     256.0       10.887546   95.104933\n",
      "2     384.0       13.433269  121.762685\n",
      "3     512.0       15.492178  149.307922\n",
      "4     640.0       18.469907  177.655309\n",
      "5     768.0       20.134320  205.909714\n",
      "6     896.0       22.653023  236.014619\n",
      "7    1024.0       24.812657  265.756011\n",
      "8    1152.0       27.017266  295.405000\n",
      "9    1280.0       28.997859  326.903552\n",
      "10   1408.0       31.143989  356.693089\n",
      "11   1536.0       33.085976  387.498528\n",
      "12   1664.0       35.679817  418.168187\n",
      "13   1792.0       37.449855  445.585936\n",
      "14   1920.0       39.451305  474.696338\n",
      "15   2048.0       41.654609  503.018677\n",
      "16   2176.0       43.877479  532.659173\n",
      "17   2304.0       46.567116  560.815573\n",
      "18   2432.0       48.318572  589.475751\n",
      "19   2560.0       50.484329  618.989170\n",
      "20   2688.0       52.832257  647.974670\n",
      "21   2816.0       54.834217  676.816761\n",
      "22   2944.0       57.589222  704.801977\n",
      "23   3072.0       59.823029  733.571351\n",
      "24   3200.0       61.515540  762.031198\n",
      "25   3328.0       63.119560  790.242136\n",
      "26   3456.0       66.149943  819.427133\n",
      "27   3584.0       68.203770  846.715748\n",
      "28   3712.0       70.039481  874.464869\n",
      "29   3840.0       72.566442  902.809203\n",
      "30   3968.0       74.741311  929.685593\n",
      "31   4096.0       76.404415  958.922625\n"
     ]
    }
   ],
   "source": [
    "\n",
    "torch.cuda.empty_cache()\n",
    "@triton.testing.perf_report(\n",
    "    triton.testing.Benchmark(\n",
    "        x_names=['seq_len'],  # argument names to use as an x-axis for the plot\n",
    "        x_vals=[128 * i for i in range(1, 32+1, 1)],  # different possible values for `x_name`\n",
    "        line_arg='provider',  # argument name whose value corresponds to a different line in the plot\n",
    "        line_vals=['FusedApplyRope', 'ApplyRope'],  # possible values for `line_arg``\n",
    "        line_names=[\n",
    "            \"FusedApplyRope\",\n",
    "            \"ApplyRope\",\n",
    "        ],  # label name for the lines\n",
    "        styles=[('blue', '-'), ('green', '-')],  # line styles\n",
    "        ylabel=\"ms\",  # label name for the y-axis\n",
    "        plot_name=\"forward\",  # name for the plot. Used also as a file name for saving the plot.\n",
    "        args={'qh':32, 'kh':32, 'head_dim': 128, 'bs': 2}\n",
    "        # args={'bs': 2, 'num_head': 32, 'rope_head_dim': 32, \n",
    "        #       'nope_head_dim': 64, 'kv_lora_rank': 256},  # values for function arguments not in `x_names` and `y_name`\n",
    "    ))\n",
    "def benchmark(bs, seq_len, head_dim,qh, kh, provider):\n",
    "    device = torch.device('cuda')\n",
    "    dtype = torch.float16\n",
    "    q = torch.randn(bs, seq_len, qh, head_dim, device=device, dtype=dtype).transpose(1,2)\n",
    "    k = torch.randn(bs, seq_len, kh, head_dim, device=device, dtype=dtype).transpose(1,2)\n",
    "    cos = torch.randn(bs, seq_len, head_dim, device=device, dtype=dtype)\n",
    "    sin = torch.randn_like(cos)\n",
    "    # cos_unsloth = torch.randn(seq_len, head_dim, device=device, dtype=dtype)\n",
    "    # sin_unsloth = torch.randn_like(cos)\n",
    "    stream = torch.cuda.Stream()\n",
    "    torch.cuda.set_stream(stream)\n",
    "\n",
    "    if provider == 'FusedApplyRope':\n",
    "        ms = triton.testing.do_bench(lambda: fused_apply_rope(q, k, cos, sin))\n",
    "    if provider == 'ApplyRope':\n",
    "        ms = triton.testing.do_bench(lambda: apply_rotary_pos_emb(q, k, cos, sin))\n",
    "\n",
    "    return ms * 1e3\n",
    "# print(f'bs: {32}, seq_len: {1024}')\n",
    "benchmark.run(show_plots=True, print_data=True)\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# backward\n",
    "- 不知道为啥，如果是GQA的话，backward会满很多，比MHA还慢。v4和v5都是"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 80,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "image/png": "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mzZtwqlTpzBixAi9dvPmzUO9evVw8eJFfPrppxg2bBiCg4P/058DEeUxISIycgEBAQJA7ty5k2lbhQoVZP369XrrZsyYIR4eHiIismTJEilWrJgkJCToti9atEgAyMWLF1/72kePHhUA8vTpUxERGThwoAwZMkSvzcmTJ8XExET3Gq6urtKrVy/ddq1WK46OjrJo0aJs9ZeIlGGmcGYjIvrPatasiZYtW6J69erw8vJCq1at8NFHH8HCwgK3b9/GwIEDMXjwYF371NRU2NnZAQCuX7+OGjVqwNLSUrfdw8Mjx7VcunQJly9fxrp163TrRARarRYhISFwd3cHANSoUUO3XaVSwdnZGRERETl+XSLKewxNRGT0TE1N4evrizNnzuDQoUNYsGABJk+ejN27dwMAli5dioYNG2Z6Tl6Ii4vDJ598gs8++yzTtrJly+p+Nzc319umUqmg1WrzpCYiyh0MTURUIKhUKjRp0gRNmjTB1KlT4erqitOnT8PFxQX//PMPvL29s3yeu7s71qxZg8TERN3RprNnz+a4jjp16uDatWuoWLFijvdBRIaJA8GJyOj5+/vju+++w4ULF3Dv3j1s27YNjx49gru7O6ZPn45Zs2Zh/vz5uHnzJq5cuYKVK1fixx9/BAD07NkTKpUKgwcPxrVr17Bv3z788MMPOa5lwoQJOHPmDEaMGIHAwEDcunULO3fuzDQQnIiMD480EZHR02g0OHHiBH7++WfExMTA1dUV8+bNQ5s2bQAA1tbWmDt3LsaPHw8bGxtUr14do0ePBgDY2tpi9+7dGDp0KGrXro2qVavi+++/R5cuXXJUS40aNXD8+HFMnjwZTZs2hYigQoUK6NatW251l4gUohIRUboIIiJDcufOHbi5ueHixYuoVauW0uUQkYHg6TkiIiKibGBoIiJ6haFDh8LW1jbLZejQoUqXR0T5iKfniIheISIiAjExMVlu02g0cHR0zOeKiEgpDE1ERERE2cDTc0RERETZwNBERERElA0MTURERETZwNBERERElA0MTURERETZwNBERERElA0MTURERETZwNBERERElA3/B1hnOU8eJoryAAAAAElFTkSuQmCC",
      "text/plain": [
       "<Figure size 640x480 with 1 Axes>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "backward:\n",
      "    seq_len  FusedApplyRope    ApplyRope\n",
      "0     128.0       71.536772   354.313761\n",
      "1     256.0      124.859698   678.329825\n",
      "2     384.0      175.174996   996.210277\n",
      "3     512.0      225.762412  1304.548860\n",
      "4     640.0      276.758522  1603.955030\n",
      "5     768.0      328.054488  1899.845481\n",
      "6     896.0      378.031909  2199.207067\n",
      "7    1024.0      430.370331  2496.592283\n",
      "8    1152.0      481.057167  2798.862934\n",
      "9    1280.0      532.589614  3097.526312\n",
      "10   1408.0      584.110260  3399.021387\n",
      "11   1536.0      635.149598  3698.171139\n",
      "12   1664.0      685.451746  3994.641066\n",
      "13   1792.0      736.880779  4300.382137\n",
      "14   1920.0      788.349807  4596.955299\n",
      "15   2048.0      839.526474  4894.154072\n",
      "16   2176.0      890.197992  5194.549561\n",
      "17   2304.0      940.973938  5499.653339\n",
      "18   2432.0      992.371023  5793.086052\n",
      "19   2560.0     1045.391440  6103.139877\n",
      "20   2688.0     1094.375730  6393.602848\n",
      "21   2816.0     1146.269441  6690.552711\n",
      "22   2944.0     1196.803331  6987.261295\n",
      "23   3072.0     1248.334050  7292.314529\n",
      "24   3200.0     1300.119042  7584.034443\n",
      "25   3328.0     1349.632025  7880.659103\n",
      "26   3456.0     1400.967479  8180.284500\n",
      "27   3584.0     1451.532483  8481.657982\n",
      "28   3712.0     1501.319170  8790.706635\n",
      "29   3840.0     1553.128242  9088.067055\n",
      "30   3968.0     1604.247451  9386.347771\n",
      "31   4096.0     1678.815961  9740.366936\n"
     ]
    }
   ],
   "source": [
    "\n",
    "torch.cuda.empty_cache()\n",
    "@triton.testing.perf_report(\n",
    "    triton.testing.Benchmark(\n",
    "        x_names=['seq_len'],  # argument names to use as an x-axis for the plot\n",
    "        x_vals=[128 * i for i in range(1, 32+1, 1)],  # different possible values for `x_name`\n",
    "        line_arg='provider',  # argument name whose value corresponds to a different line in the plot\n",
    "        line_vals=['FusedApplyRope', 'ApplyRope'],  # possible values for `line_arg``\n",
    "        line_names=[\n",
    "            \"FusedApplyRope\",\n",
    "            \"ApplyRope\",\n",
    "        ],  # label name for the lines\n",
    "        styles=[('blue', '-'), ('green', '-')],  # line styles\n",
    "        ylabel=\"ms\",  # label name for the y-axis\n",
    "        plot_name=\"backward\",  # name for the plot. Used also as a file name for saving the plot.\n",
    "        args={'qh':32, 'kh':32, 'head_dim': 128, 'bs': 8}\n",
    "        # args={'bs': 2, 'num_head': 32, 'rope_head_dim': 32, \n",
    "        #       'nope_head_dim': 64, 'kv_lora_rank': 256},  # values for function arguments not in `x_names` and `y_name`\n",
    "    ))\n",
    "def benchmark(bs, seq_len, head_dim,qh, kh, provider):\n",
    "    device = torch.device('cuda')\n",
    "    dtype = torch.float16\n",
    "    q = torch.randn(bs, seq_len, qh, head_dim, device=device, dtype=dtype).transpose(1,2)\n",
    "    k = torch.randn(bs, seq_len, kh, head_dim, device=device, dtype=dtype).transpose(1,2)\n",
    "    q.requires_grad_(True)\n",
    "    k.requires_grad_(True)\n",
    "    cos = torch.randn(bs, seq_len, head_dim, device=device, dtype=dtype)\n",
    "    sin = torch.randn_like(cos)\n",
    "    stream = torch.cuda.Stream()\n",
    "    torch.cuda.set_stream(stream)\n",
    "    if provider == 'FusedApplyRope':\n",
    "        a,b = fused_apply_rope(q, k, cos, sin)\n",
    "        loss = (a * repeat_kv(b, qh//kh)).sum()\n",
    "        ms = triton.testing.do_bench(lambda: loss.backward(retain_graph=True), grad_to_none=[q,k])\n",
    "    if provider == 'ApplyRope':\n",
    "        a,b = apply_rotary_pos_emb(q, k, cos, sin)\n",
    "        loss = (a * repeat_kv(b, qh//kh)).sum()\n",
    "        ms = triton.testing.do_bench(lambda: loss.backward(retain_graph=True), grad_to_none=[q,k])\n",
    "\n",
    "    return ms * 1e3\n",
    "# print(f'bs: {32}, seq_len: {1024}')\n",
    "benchmark.run(show_plots=True, print_data=True)\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "image/png": "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UZL+q48JCQmibdu2wtbWVpiamgo3NzcxceJErTlsdL2PQ4YMEa6ursLExETY29uLDh06aMKQEGX/PRTi+X3Uh/fwaR4PRPrwPhbiavdERERU7nEMEREREZV7DERERERU7jEQERERUbnHQERERETlHgMRERERlXsMRERERFTuMRARERFRucdAREREROUeAxER0TPExMRAoVA8sXglEekfBiIiIiIq9xiIiIiIqNxjICIinbdu3Tp4eHjA3NwcdnZ28Pb2RkZGBgBg6dKlqF+/PszMzFCvXj38/PPPWo89fvw43njjDZiZmaFZs2bYuHEjFAoF4uLiXqmWQ4cOoU2bNjA3N0fVqlXx4YcfamoBgOrVq2P27NkYMmQIKlSogGrVquGXX3555b4TUelgICIinXb37l0EBwdjyJAhuHTpEmJiYtC7d28IIRAWFoapU6fiyy+/xKVLlzB79mx8/vnnWLFiBQAgPT0dXbt2hbu7O06dOoXp06fjf//73yvXcu3aNXTq1Al+fn44e/Ys1qxZg0OHDmHMmDFa7b799ls0a9YMp0+fxvvvv49Ro0YhPj7+tX4PRFTCBBGRDjt16pQAIG7cuPHEvlq1aonw8HCtbbNmzRJeXl5CCCGWLFki7OzsRFZWlmb/okWLBABx+vTp/3ztffv2CQDi4cOHQgghhg4dKkaMGKHV5uDBg8LAwEDzGq6urqJfv36a/Wq1Wjg4OIhFixa9UH+JSB5GMucxIqLnatSoETp06AAPDw/4+PigY8eO8Pf3h4mJCa5du4ahQ4di+PDhmvb5+fmwtrYGAFy6dAmenp4wMzPT7Pfy8nrlWs6cOYOzZ88iLCxMs00IAbVajevXr6N+/foAAE9PT81+hUIBJycnJCcnv/LrElHJYyAiIp1maGiI6OhoHDlyBLt378aCBQswZcoUbN26FQDw66+/okWLFk88piSkp6fjvffew4cffvjEvmrVqml+NjY21tqnUCigVqtLpCYiKh4MRESk8xQKBVq1aoVWrVph6tSpcHV1xeHDh+Hi4oK///4bISEhT31c/fr1sXLlSmRnZ2uOEh09evSV62jSpAkuXrwINze3V34OItJNHFRNRDrt2LFjmD17Nk6ePImEhARs2LAB9+7dQ/369TFjxgzMmTMHP/74I65cuYJz585h+fLl+O677wAAffv2hUKhwPDhw3Hx4kVs374d33zzzSvXMmnSJBw5cgRjxoxBXFwc/vrrL2zevPmJQdVEVPbwCBER6TSlUokDBw7ghx9+gEqlgqurK7799lt07twZAGBhYYGvv/4aEydOhKWlJTw8PDBu3DgAgJWVFbZu3YqRI0fijTfegLu7O7766iv4+fm9Ui2enp7Yv38/pkyZgjZt2kAIgVq1aiEwMLC4uktEMlEIIYTcRRARlZYbN26gRo0aOH36NBo3bix3OUSkI3jKjIiIiMo9BiIiKrdGjhwJKyurp95Gjhwpd3lEVIp4yoyIyq3k5GSoVKqn7lMqlXBwcCjliohILgxEREREVO7xlBkRERGVewxEREREVO4xEBEREVG5x0BERERE5R4DEREREZV7DERERERU7jEQERERUbnHQERERETl3v8BAzzDrsp1udkAAAAASUVORK5CYII=",
      "text/plain": [
       "<Figure size 640x480 with 1 Axes>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "backward:\n",
      "    seq_len  FusedApplyRope    ApplyRope\n",
      "0     128.0      281.193316   351.407826\n",
      "1     256.0      270.735890   351.938546\n",
      "2     384.0      269.675225   446.875662\n",
      "3     512.0      277.122408   581.544459\n",
      "4     640.0      334.372938   710.655987\n",
      "5     768.0      394.270897   842.100501\n",
      "6     896.0      458.595008   989.461362\n",
      "7    1024.0      525.929153  1134.757876\n",
      "8    1152.0      601.164639  1284.880877\n",
      "9    1280.0      670.698345  1425.306797\n",
      "10   1408.0      742.008269  1567.636132\n",
      "11   1536.0      801.861703  1699.652195\n",
      "12   1664.0      863.411129  1831.454039\n",
      "13   1792.0      925.185144  1962.548256\n",
      "14   1920.0      982.792199  2091.168165\n",
      "15   2048.0     1045.111775  2220.726490\n",
      "16   2176.0     1104.614854  2348.866701\n",
      "17   2304.0     1166.715145  2480.809689\n",
      "18   2432.0     1226.299882  2608.220339\n",
      "19   2560.0     1289.021134  2739.694357\n",
      "20   2688.0     1346.067309  2863.408089\n",
      "21   2816.0     1405.915260  2995.761395\n",
      "22   2944.0     1468.086600  3121.158838\n",
      "23   3072.0     1527.250290  3249.332428\n",
      "24   3200.0     1589.734912  3381.698370\n",
      "25   3328.0     1647.404075  3507.904053\n",
      "26   3456.0     1712.535143  3641.780615\n",
      "27   3584.0     1770.384192  3768.566847\n",
      "28   3712.0     1833.009839  3901.729584\n",
      "29   3840.0     1892.150164  4028.673649\n",
      "30   3968.0     1952.961564  4157.304764\n",
      "31   4096.0     2007.822037  4281.722069\n"
     ]
    }
   ],
   "source": [
    "\n",
    "torch.cuda.empty_cache()\n",
    "@triton.testing.perf_report(\n",
    "    triton.testing.Benchmark(\n",
    "        x_names=['seq_len'],  # argument names to use as an x-axis for the plot\n",
    "        x_vals=[128 * i for i in range(1, 32+1, 1)],  # different possible values for `x_name`\n",
    "        line_arg='provider',  # argument name whose value corresponds to a different line in the plot\n",
    "        line_vals=['FusedApplyRope', 'ApplyRope'],  # possible values for `line_arg``\n",
    "        line_names=[\n",
    "            \"FusedApplyRope\",\n",
    "            \"ApplyRope\",\n",
    "        ],  # label name for the lines\n",
    "        styles=[('blue', '-'), ('green', '-')],  # line styles\n",
    "        ylabel=\"ms\",  # label name for the y-axis\n",
    "        plot_name=\"backward\",  # name for the plot. Used also as a file name for saving the plot.\n",
    "        args={'qh':16, 'kh':8, 'head_dim': 128, 'bs': 8}\n",
    "        # args={'bs': 2, 'num_head': 32, 'rope_head_dim': 32, \n",
    "        #       'nope_head_dim': 64, 'kv_lora_rank': 256},  # values for function arguments not in `x_names` and `y_name`\n",
    "    ))\n",
    "def benchmark(bs, seq_len, head_dim,qh, kh, provider):\n",
    "    device = torch.device('cuda')\n",
    "    dtype = torch.float16\n",
    "    q = torch.randn(bs, seq_len, qh, head_dim, device=device, dtype=dtype).transpose(1,2)\n",
    "    k = torch.randn(bs, seq_len, kh, head_dim, device=device, dtype=dtype).transpose(1,2)\n",
    "    q.requires_grad_(True)\n",
    "    k.requires_grad_(True)\n",
    "    cos = torch.randn(bs, seq_len, head_dim, device=device, dtype=dtype)\n",
    "    sin = torch.randn_like(cos)\n",
    "    stream = torch.cuda.Stream()\n",
    "    torch.cuda.set_stream(stream)\n",
    "    if provider == 'FusedApplyRope':\n",
    "        a,b = fused_apply_rope(q, k, cos, sin)\n",
    "        loss = (a.contiguous() * repeat_kv(b, qh//kh).contiguous()).sum()\n",
    "        ms = triton.testing.do_bench(lambda: loss.backward(retain_graph=True), grad_to_none=[q,k])\n",
    "    if provider == 'ApplyRope':\n",
    "        a,b = apply_rotary_pos_emb(q, k, cos, sin)\n",
    "        loss = (a.contiguous() * repeat_kv(b, qh//kh).contiguous()).sum()\n",
    "        ms = triton.testing.do_bench(lambda: loss.backward(retain_graph=True), grad_to_none=[q,k])\n",
    "\n",
    "    return ms * 1e3\n",
    "# print(f'bs: {32}, seq_len: {1024}')\n",
    "benchmark.run(show_plots=True, print_data=True)\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# forward + backward"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 4,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "image/png": "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",
      "text/plain": [
       "<Figure size 640x480 with 1 Axes>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "forward + backward:\n",
      "    seq_len  FusedApplyRope     ApplyRope\n",
      "0     128.0      331.267864    720.717549\n",
      "1     256.0      251.625836   1159.904003\n",
      "2     384.0      319.613069   1700.867534\n",
      "3     512.0      410.856277   2227.354765\n",
      "4     640.0      501.963377   2737.264156\n",
      "5     768.0      590.719163   3247.121811\n",
      "6     896.0      682.296693   3757.365704\n",
      "7    1024.0      772.882819   4260.835171\n",
      "8    1152.0      862.961590   4788.694382\n",
      "9    1280.0      951.535940   5298.236847\n",
      "10   1408.0     1043.775797   5811.450005\n",
      "11   1536.0     1133.754969   6322.833061\n",
      "12   1664.0     1222.221494   6830.530643\n",
      "13   1792.0     1338.012457   7372.162819\n",
      "14   1920.0     1424.792886   7876.528263\n",
      "15   2048.0     1511.939526   8382.307053\n",
      "16   2176.0     1600.391865   8896.050453\n",
      "17   2304.0     1686.105847   9408.796310\n",
      "18   2432.0     1776.590347   9900.398254\n",
      "19   2560.0     1862.151980  10427.466393\n",
      "20   2688.0     1952.623963  10920.503616\n",
      "21   2816.0     2035.525322  11432.864189\n",
      "22   2944.0     2126.165628  11933.691978\n",
      "23   3072.0     2216.921091  12448.209763\n",
      "24   3200.0     2306.257725  12960.082054\n",
      "25   3328.0     2396.420240  13433.864594\n",
      "26   3456.0     2484.635830  13984.143257\n",
      "27   3584.0     2589.884520  14508.458138\n",
      "28   3712.0     2675.943136  15043.135643\n",
      "29   3840.0     2770.107031  15539.727211\n",
      "30   3968.0     2856.453180  16049.753189\n",
      "31   4096.0     3002.584934  16637.828827\n"
     ]
    }
   ],
   "source": [
    "\n",
    "torch.cuda.empty_cache()\n",
    "@triton.testing.perf_report(\n",
    "    triton.testing.Benchmark(\n",
    "        x_names=['seq_len'],  # argument names to use as an x-axis for the plot\n",
    "        x_vals=[128 * i for i in range(1, 32+1, 1)],  # different possible values for `x_name`\n",
    "        line_arg='provider',  # argument name whose value corresponds to a different line in the plot\n",
    "        line_vals=['FusedApplyRope', 'ApplyRope'],  # possible values for `line_arg``\n",
    "        line_names=[\n",
    "            \"FusedApplyRope\",\n",
    "            \"ApplyRope\",\n",
    "        ],  # label name for the lines\n",
    "        styles=[('blue', '-'), ('green', '-')],  # line styles\n",
    "        ylabel=\"ms\",  # label name for the y-axis\n",
    "        plot_name=\"forward + backward\",  # name for the plot. Used also as a file name for saving the plot.\n",
    "        args={'qh':32, 'kh':32, 'head_dim': 128, 'bs': 8}\n",
    "        # args={'bs': 2, 'num_head': 32, 'rope_head_dim': 32, \n",
    "        #       'nope_head_dim': 64, 'kv_lora_rank': 256},  # values for function arguments not in `x_names` and `y_name`\n",
    "    ))\n",
    "def benchmark(bs, seq_len, head_dim,qh, kh, provider):\n",
    "    device = torch.device('cuda')\n",
    "    dtype = torch.float16\n",
    "    q = torch.randn(bs, seq_len, qh, head_dim, device=device, dtype=dtype).transpose(1,2)\n",
    "    k = torch.randn(bs, seq_len, kh, head_dim, device=device, dtype=dtype).transpose(1,2)\n",
    "    q.requires_grad_(True)\n",
    "    k.requires_grad_(True)\n",
    "    cos = torch.randn(bs, seq_len, head_dim, device=device, dtype=dtype)\n",
    "    sin = torch.randn_like(cos)\n",
    "    stream = torch.cuda.Stream()\n",
    "    torch.cuda.set_stream(stream)\n",
    "\n",
    "    def fwd_bwd(func, *args):\n",
    "        a,b = func(*args)\n",
    "        loss = (a * repeat_kv(b, qh//kh)).sum()\n",
    "        loss.backward(retain_graph=True)\n",
    "\n",
    "    if provider == 'FusedApplyRope':\n",
    "        ms = triton.testing.do_bench(lambda: fwd_bwd(fused_apply_rope, q, k, cos, sin), grad_to_none=[q,k])\n",
    "    if provider == 'ApplyRope':\n",
    "        ms = triton.testing.do_bench(lambda: fwd_bwd(apply_rotary_pos_emb, q, k, cos, sin), grad_to_none=[q,k])\n",
    "\n",
    "    return ms * 1e3\n",
    "# print(f'bs: {32}, seq_len: {1024}')\n",
    "benchmark.run(show_plots=True, print_data=True)\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": []
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.12.8"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 2
}
